class FsmState:
    def enter(self, event, fsm):  ## 参数event为触发状态转换的事件, fsm则为状态所在状态机
        pass
    
    def exit(self, fsm):
        pass




class DvdPowerOn(FsmState):
    def enter(self, event, fsm):
        print("dvd is power on")

class DvdPlaying(FsmState):
    def enter(self, event, fsm):
        print("dvd is going to play")

    def exit(self, fsm):
        print("dvd stoped play")

class DvdPausing(FsmState):
    def enter(self, event, fsm):
        print("dvd is going to pause")

    def exit(self, fsm):
        print("dvd stopped pause")

class DvdPowerOff(FsmState):
    def enter(self, event, fsm):
        print("dvd is power off")



class FsmEvent:
    pass

class PowerOnEvent(FsmEvent):
    pass

class PowerOffEvent(FsmEvent):
    pass

class PlayEvent(FsmEvent):
    pass

class PauseEvent(FsmEvent):
    pass

class FsmException(Exception):
    def __init__(self, description):
        super().__init__(description)


from collections import namedtuple

Transaction = namedtuple("Transaction", ["prev_state", "event", "next_state"])    ## 一次状态转换的所有要素：上一个状态--事件-->下一个状态

class FSM:
    def __init__(self, context):                                                  ## context：状态机上下文
        self.context = context
        self.state_transaction_table = []                                         ## 常规状态转换表
        self.global_transaction_table = []                                        ## 全局状态转换表
        self.current_state = None
        self.working_state = FsmState

    def add_global_transaction(self, event, end_state):     # 全局转换，直接进入到结束状态
        if not issubclass(end_state, FsmFinalState):
            raise FsmException("The state should be FsmFinalState")
        self.global_transaction_table.append(Transaction(self.working_state, event, end_state))

    def add_transaction(self, prev_state, event, next_state):
        if issubclass(prev_state, FsmFinalState):
            raise FsmException("It's not allowed to add transaction after Final State Node")
        self.state_transaction_table.append(Transaction(prev_state, event, next_state))

    def process_event(self, event):
        for transaction in self.global_transaction_table:
            if isinstance(event, transaction.event):
                self.current_state = transaction.next_state()
                self.current_state.enter(event, self)
                self.clear_transaction_table()
                return
        for transaction in self.state_transaction_table:
            if isinstance(self.current_state, transaction.prev_state) and isinstance(event, transaction.event):
                self.current_state.exit(self.context)
                self.current_state = transaction.next_state()
                self.current_state.enter(event, self)
                if isinstance(self.current_state, FsmFinalState):
                    self.clear_transaction_table()
                return
        raise FsmException("Transaction not found")
    
    def clear_transaction_table(self):
        self.global_transaction_table = []
        self.state_transaction_table = []
        self.current_state = None

    def run(self):
        if len(self.state_transaction_table) == 0: return
        self.current_state = self.state_transaction_table[0].prev_state()
        self.current_state.enter(None, self)

    def isRunning(self):
        return self.current_state is not None

    def next_state(self, event):
        for transaction in self.global_transaction_table:
            if isinstance(event, transaction.event):
                return transaction.next_state
        for transaction in self.state_transaction_table:
            if isinstance(self.current_state, transaction.prev_state) and isinstance(event, transaction.event):
                return transaction.next_state
        return None






class DVD(FSM):
    def __init__(self):
        super().__init__(None)



dvd = DVD();
dvd.add_transaction(DvdPowerOff, PowerOnEvent, DvdPowerOn)
dvd.add_transaction(DvdPowerOn, PowerOffEvent, DvdPowerOff)
dvd.add_transaction(DvdPowerOn, PlayEvent, DvdPlaying)
dvd.add_transaction(DvdPlaying, PowerOffEvent, DvdPowerOff)
dvd.add_transaction(DvdPlaying, PauseEvent, DvdPausing)
dvd.add_transaction(DvdPausing, PowerOffEvent, DvdPowerOff)
dvd.add_transaction(DvdPausing, PlayEvent, DvdPlaying)
dvd.run()

dvd.process_event(PowerOnEvent())
dvd.process_event(PlayEvent())
dvd.process_event(PauseEvent())
dvd.process_event(PlayEvent())
dvd.process_event(PowerOffEvent())


